package com.unnet.yjs.util;

import com.unnet.yjs.base.ContainerProperties;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.errors.*;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.xmlpull.v1.XmlPullParserException;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Email: love1208tt@foxmail.com
 * Copyright (c)  2019. missbe
 *
 * @author lyg   19-7-2 上午11:20
 **/
@Component
public class MinIoOssOperation implements IOssOperation {
    private static final Logger LOGGER = LoggerFactory.getLogger(MinIoOssOperation.class);
    @Resource
    @Qualifier("containerProperties")
    private ContainerProperties containerProperties;

    private static class MinIoConnection {
        private int id;
        private boolean isBusy = false;
        private MinioClient minioClient;
    }

    private static List<MinIoConnection> pools;

    private void initialPool() {
        pools = new ArrayList<>(20);
        for (int i = 0; i < 20; i++) {
            MinIoConnection m = new MinIoConnection();
            m.id = i + 1;
            m.isBusy = false;
            try {
                m.minioClient = getClient();
            } catch (InvalidPortException | InvalidEndpointException e) {
                e.printStackTrace();
                LOGGER.error("MinioClient对象获取失败->" + e.getLocalizedMessage());
            }
            pools.add(m);
        }
    }

    private MinIoConnection getMinIoConnection() {
        if (pools == null) {
            initialPool();
        }
        while (true) {
            for (MinIoConnection m : pools) {
                if (!m.isBusy) {
                    ///设置当前连接忙
                    m.isBusy = true;
                    return m;
                }
            }///end for
            try {
                Thread.sleep(2000);
                LOGGER.info("当前没有空闲连接，等待2s后再试；");
            } catch (InterruptedException e) {
                LOGGER.error("睡眠被打断.msg:{}", e.getLocalizedMessage());
            }///end try
        }
    }

    private void backConnection(MinIoConnection minIoConnection) {
        for (MinIoConnection connection : pools) {
            if (connection.id == minIoConnection.id) {
                ///设置当前连接空闲，可以继续使用
                connection.isBusy = false;
                break;
            }
        }
    }

    /**
     * 获取对象存储客户端连接对象
     *
     * @return 客户端对象
     * @throws InvalidPortException     端口无效异常
     * @throws InvalidEndpointException 端点无效异常
     */
    private MinioClient getClient() throws InvalidPortException, InvalidEndpointException {
        LOGGER.info("初始化MyOss对象MinioClient对象开始.");
        LOGGER.info("access=key" + containerProperties.getOssAccessKey());
        LOGGER.info("secret=key" + containerProperties.getOssSecretKey());
        LOGGER.info("bucket=key" + containerProperties.getOssBucketName());
        MinioClient minioClient;

        minioClient = new MinioClient(containerProperties.getOssEndPoint(), containerProperties.getOssAccessKey(), containerProperties.getOssSecretKey());

        LOGGER.info("初始化MyOss对象MinioClient对象结束.");
        return minioClient;
    }

    /**
     * 上传文件到对象存储库
     *
     * @param stream   源文件流
     * @param fileSize 源文件大小
     * @param filename 源文件名称
     */
    @Override
    public boolean store(InputStream stream, long fileSize, String filename) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(stream, baos);
        } catch (IOException e) {
            LOGGER.error("IOUtils复制输入流到输出流失败->" + e.getLocalizedMessage());
            return false;
        }
        byte[] bytes = baos.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        MinIoConnection connection = getMinIoConnection();
        ///判断空指针异常
        Objects.requireNonNull(connection);

        MinioClient minioClient = connection.minioClient;

        ///判断空指针异常
        Objects.requireNonNull(minioClient);
        boolean isExist = minioClient.bucketExists(containerProperties.getOssBucketName());
        if (isExist) {
            LOGGER.info(containerProperties.getOssBucketName() + " already exists.");
        } else {
            minioClient.makeBucket(containerProperties.getOssBucketName());
        }

        try {
            minioClient.putObject(containerProperties.getOssBucketName(), filename, bais, fileSize, null, null, null);
            LOGGER.info("MinioClient上传文件" + filename + "完成.");
            ////返回连接到连接池
            backConnection(connection);
            return true;
        } catch (InvalidBucketNameException | NoSuchAlgorithmException | IOException | InvalidKeyException | NoResponseException | XmlPullParserException | ErrorResponseException | InternalException | InvalidArgumentException | InsufficientDataException e) {
            LOGGER.info("MinioClient上传文件" + filename + "失败.Error:" + e.getLocalizedMessage());
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 从对象存储中根据文件名称获取文件流
     *
     * @param filename 文件名
     * @return 文件流
     */
    @Override
    public InputStream load(String filename) throws Exception {
        MinioClient minioClient;
        MinIoConnection connection = getMinIoConnection();
        ///判断空指针异常
        Objects.requireNonNull(connection);
        minioClient = connection.minioClient;
        ///判断空指针异常
        Objects.requireNonNull(minioClient);
        InputStream is = minioClient.getObject(containerProperties.getOssBucketName(), filename);
        LOGGER.info("MinioClient加载文件" + filename + "完成.");
        ////返回连接到连接池
        backConnection(connection);
        return is;
    }

    @Override
    public InputStream load(String filename, long offset, long length) throws Exception {
        MinioClient minioClient;
        MinIoConnection connection = getMinIoConnection();
        ///判断空指针异常
        Objects.requireNonNull(connection);
        minioClient = connection.minioClient;
        ///判断空指针异常
        Objects.requireNonNull(minioClient);
        InputStream is = minioClient.getObject(containerProperties.getOssBucketName(), filename, offset, length);
        LOGGER.info("MinioClient加载文件" + filename + ",起始位置：" + offset + ",长度：" + length + "完成.");
        ////返回连接到连接池
        backConnection(connection);
        return is;
    }

    @Override
    public boolean remove(List<String> fileNames) {
        MinioClient minioClient;
        MinIoConnection connection = getMinIoConnection();
        ///判断空指针异常
        Objects.requireNonNull(connection);
        minioClient = connection.minioClient;
        ///判断空指针异常
        Objects.requireNonNull(minioClient);
        for (String fileName : fileNames) {
            try {
                minioClient.removeObject(containerProperties.getOssBucketName(), fileName);
                LOGGER.info("进行删除对象存储中文件操作:" + fileName);
            } catch (Exception e) {
                e.printStackTrace();
                LOGGER.info("进行删除对象存储文件出错，MSG:" + e.getLocalizedMessage());
            }///end try
        }///end for
        ////返回连接到连接池
        backConnection(connection);
        return true;
    }

    @Override
    public long length(String filename) throws Exception {
        MinioClient minioClient;
        MinIoConnection connection = getMinIoConnection();
        ///判断空指针异常
        Objects.requireNonNull(connection);
        minioClient = connection.minioClient;
        ///判断空指针异常
        Objects.requireNonNull(minioClient);
        ObjectStat metaObject = minioClient.statObject(containerProperties.getOssBucketName(), filename);
        long length = metaObject.length();
        ////返回连接到连接池
        backConnection(connection);
        return length;
    }
}
